/*
 * Copyright (C) 2015 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.data.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import traderlib.core.data.db.connection.ConnectionInfo;
import traderlib.core.data.db.connection.ConnectionPool;
import traderlib.core.data.db.sql.AddCheck;
import traderlib.core.data.db.sql.AddColumn;
import traderlib.core.data.db.sql.AddForeignKey;
import traderlib.core.data.db.sql.AddPrimaryKey;
import traderlib.core.data.db.sql.CreateIndex;
import traderlib.core.data.db.sql.CreateTable;
import traderlib.core.data.db.sql.Delete;
import traderlib.core.data.db.sql.DropForeignKey;
import traderlib.core.data.db.sql.DropIndex;
import traderlib.core.data.db.sql.DropTable;
import traderlib.core.data.db.sql.Insert;
import traderlib.core.data.db.sql.Select;
import traderlib.core.data.db.sql.Statement;
import traderlib.core.data.db.sql.Update;
import traderlib.core.data.types.OrderKey;
import traderlib.core.data.types.Value;

/**
 * A database engine represent a backend JDBC database.
 *
 * @author Miquel Sas
 */
public class DBEngine {

	/**
	 * The connection information.
	 */
	private ConnectionInfo connectionInfo;
	/**
	 * The database adapter.
	 */
	private DatabaseAdapter databaseAdapter;
	/**
	 * The connection pool.
	 */
	private ConnectionPool connectionPool;

	/**
	 * Default constructor.
	 */
	public DBEngine() {
		super();
	}

	public ConnectionPool getConnectionPool() {
		if (connectionPool == null) {
			connectionPool = new ConnectionPool(getConnectionInfo());
		}
		return connectionPool;
	}

	/**
	 * Returns the connection information.
	 *
	 * @return The connection information.
	 */
	public ConnectionInfo getConnectionInfo() {
		return connectionInfo;
	}

	/**
	 * Sets the connection information.
	 *
	 * @param connectionInfo The connection information.
	 */
	public void setConnectionInfo(ConnectionInfo connectionInfo) {
		this.connectionInfo = connectionInfo;
	}

	/**
	 * Returns the database adapter.
	 *
	 * @return The database adapter.
	 */
	public DatabaseAdapter getDatabaseAdapter() {
		return databaseAdapter;
	}

	/**
	 * Set the database adapter.
	 *
	 * @param databaseAdapter The database adapter.
	 */
	public void setDatabaseAdapter(DatabaseAdapter databaseAdapter) {
		this.databaseAdapter = databaseAdapter;
	}

	/**
	 * Returns the connection.
	 *
	 * @return The connection.
	 * @throws SQLException
	 */
	public Connection getConnection() throws SQLException {
		return getConnectionPool().getConnection();
	}

	/**
	 * Execute a stement, not a Select.
	 *
	 * @param statement A statement.
	 * @return The number of records modified.
	 * @throws SQLException
	 */
	public int executeStatement(Statement statement) throws SQLException {
		int count = 0;
		Connection cn = null;
		try {
			cn = getConnection();
			count = executeStatement(statement, cn);
		} finally {
			if (cn != null && !cn.isClosed()) {
				cn.close();
			}
		}
		return count;
	}

	/**
	 * Execute a statement, not Select.
	 *
	 * @param statement The statement.
	 * @param cn The connection.
	 * @return The number of records modified.
	 * @throws SQLException
	 */
	public int executeStatement(Statement statement, Connection cn) throws SQLException {
		if (statement.getClass() == Select.class) {
			throw new SQLException("Select statements not supported");
		}
		int count = 0;
		PreparedStatement ps = null;
		try {
			cn = getConnection();
			String sql = statement.toSQL();
			ps = cn.prepareStatement(sql);
			List<Value> values = statement.getValues();
			for (int i = 0; i < values.size(); i++) {
				Value value = values.get(i);
				DBUtils.toPreparedStatement(value, i + 1, ps);
			}
			count = ps.executeUpdate();
		} finally {
			if (ps != null && !ps.isClosed()) {
				ps.close();
			}
		}
		return count;
	}

	/**
	 * Executes a list of statements.
	 *
	 * @param statements The list of statements.
	 * @param cn The connection.
	 * @return The number of records modified.
	 * @throws SQLException
	 */
	public int executeStatements(List<Statement> statements, Connection cn) throws SQLException {
		int count = 0;
		for (Statement statement : statements) {
			count += executeStatement(statement, cn);
		}
		return count;
	}

	/**
	 * Executes a list of statements within a transaction.
	 *
	 * @param statements The listof statements
	 * @return The number of records modified.
	 * @throws SQLException
	 */
	public int executeTransaction(List<Statement> statements) throws SQLException {
		Connection cn = null;
		int countModified = 0;
		try {
			cn = getConnection();
			cn.setAutoCommit(false);
			countModified = executeStatements(statements, cn);
			cn.commit();
		} catch (SQLException e) {
			if (cn != null && !cn.isClosed()) {
				cn.rollback();
			}
			throw e;
		} finally {
			if (cn != null && !cn.isClosed()) {
				cn.setAutoCommit(true);
				cn.close();
			}
		}
		return countModified;
	}

	/**
	 * Executes an add check statement.
	 *
	 * @param table The table
	 * @param column The column
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddCheck(Table table, Column column) throws SQLException {
		AddCheck addCheck = getDatabaseAdapter().getStatementAddCheck();
		addCheck.setTable(table);
		addCheck.setColumn(column);
		return executeStatement(addCheck);
	}

	/**
	 * Executes an add check statement.
	 *
	 * @param table The table.
	 * @param column The column.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddCheck(Table table, Column column, Connection cn) throws SQLException {
		AddCheck addCheck = getDatabaseAdapter().getStatementAddCheck();
		addCheck.setTable(table);
		addCheck.setColumn(column);
		return executeStatement(addCheck, cn);
	}

	/**
	 * Executes an add column statement.
	 *
	 * @param table The table
	 * @param column The column
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddColumn(Table table, Column column) throws SQLException {
		AddColumn addColumn = getDatabaseAdapter().getStatementAddColumn();
		addColumn.setTable(table);
		addColumn.setColumn(column);
		return executeStatement(addColumn);
	}

	/**
	 * Executes an add column statement.
	 *
	 * @param table The table.
	 * @param column The column.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddColumn(Table table, Column column, Connection cn) throws SQLException {
		AddColumn addColumn = getDatabaseAdapter().getStatementAddColumn();
		addColumn.setTable(table);
		addColumn.setColumn(column);
		return executeStatement(addColumn, cn);
	}

	/**
	 * Executes an add foreign key statement.
	 *
	 * @param table The table.
	 * @param foreignKey The foreign key.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddForeignKey(Table table, ForeignKey foreignKey) throws SQLException {
		AddForeignKey addForeignKey = getDatabaseAdapter().getStatementAddForeignKey();
		addForeignKey.setTable(table);
		addForeignKey.setForeignKey(foreignKey);
		return executeStatement(addForeignKey);
	}

	/**
	 * Executes an add foreign key statement.
	 *
	 * @param table The table.
	 * @param foreignKey The foreign key.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddForeignKey(Table table, ForeignKey foreignKey, Connection cn) throws SQLException {
		AddForeignKey addForeignKey = getDatabaseAdapter().getStatementAddForeignKey();
		addForeignKey.setTable(table);
		addForeignKey.setForeignKey(foreignKey);
		return executeStatement(addForeignKey, cn);
	}

	/**
	 * Executes an add primary key statement.
	 *
	 * @param table The table.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddPrimaryKey(Table table) throws SQLException {
		AddPrimaryKey addPrimaryKey = getDatabaseAdapter().getStatementAddPrimaryKey();
		addPrimaryKey.setTable(table);
		return executeStatement(addPrimaryKey);
	}

	/**
	 * Executes an add primary key statement.
	 *
	 * @param table The table.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeAddPrimaryKey(Table table, Connection cn) throws SQLException {
		AddPrimaryKey addPrimaryKey = getDatabaseAdapter().getStatementAddPrimaryKey();
		addPrimaryKey.setTable(table);
		return executeStatement(addPrimaryKey, cn);
	}

	/**
	 * Executes a create index statement.
	 *
	 * @param index The index to create.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeCreateIndex(Index index) throws SQLException {
		CreateIndex createIndex = getDatabaseAdapter().getStatementCreateIndex();
		createIndex.setIndex(index);
		return executeStatement(createIndex);
	}

	/**
	 * Executes a create index statement.
	 *
	 * @param index The index to create.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeCreateIndex(Index index, Connection cn) throws SQLException {
		CreateIndex createIndex = getDatabaseAdapter().getStatementCreateIndex();
		createIndex.setIndex(index);
		return executeStatement(createIndex, cn);
	}

	/**
	 * Executes a create table statement.
	 *
	 * @param table The table.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeCreateTable(Table table) throws SQLException {
		CreateTable createTable = getDatabaseAdapter().getStatementCreateTable();
		createTable.setTable(table);
		return executeStatement(createTable);
	}

	/**
	 * Executes a create table statement.
	 *
	 * @param table The table.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeCreateTable(Table table, Connection cn) throws SQLException {
		CreateTable createTable = getDatabaseAdapter().getStatementCreateTable();
		createTable.setTable(table);
		return executeStatement(createTable, cn);
	}

	/**
	 * Executes a delete statement.
	 *
	 * @param table The table.
	 * @param filter The filter.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDelete(Table table, Filter filter) throws SQLException {
		Delete delete = getDatabaseAdapter().getStatementDelete();
		delete.setTable(table);
		delete.setFilter(filter);
		return executeStatement(delete);
	}

	/**
	 * Executes a delete statement.
	 *
	 * @param table The table.
	 * @param filter The filter.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDelete(Table table, Filter filter, Connection cn) throws SQLException {
		Delete delete = getDatabaseAdapter().getStatementDelete();
		delete.setTable(table);
		delete.setFilter(filter);
		return executeStatement(delete, cn);
	}

	/**
	 * Executes a delete statement.
	 *
	 * @param table The table.
	 * @param record The record.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDelete(Table table, Record record) throws SQLException {
		Delete delete = getDatabaseAdapter().getStatementDelete();
		delete.setTable(table);
		delete.setRecord(record);
		return executeStatement(delete);
	}

	/**
	 * Executes a delete statement.
	 *
	 * @param table The table.
	 * @param record The record.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDelete(Table table, Record record, Connection cn) throws SQLException {
		Delete delete = getDatabaseAdapter().getStatementDelete();
		delete.setTable(table);
		delete.setRecord(record);
		return executeStatement(delete, cn);
	}

	/**
	 * Executes a drop foreign key statement.
	 *
	 * @param table The table.
	 * @param foreignKey The foreign key to drop.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropForeignKey(Table table, ForeignKey foreignKey) throws SQLException {
		DropForeignKey dropForeignKey = getDatabaseAdapter().getStatementDropForeignKey();
		dropForeignKey.setTable(table);
		dropForeignKey.setForeignKey(foreignKey);
		return executeStatement(dropForeignKey);
	}

	/**
	 * Executes a drop foreign key statement.
	 *
	 * @param table The table.
	 * @param foreignKey The foreign key to drop.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropForeignKey(Table table, ForeignKey foreignKey, Connection cn) throws SQLException {
		DropForeignKey dropForeignKey = getDatabaseAdapter().getStatementDropForeignKey();
		dropForeignKey.setTable(table);
		dropForeignKey.setForeignKey(foreignKey);
		return executeStatement(dropForeignKey, cn);
	}

	/**
	 * Executes a drop index statement.
	 *
	 * @param index The index to drop.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropIndex(Index index) throws SQLException {
		DropIndex dropIndex = getDatabaseAdapter().getStatementDropIndex();
		dropIndex.setIndex(index);
		return executeStatement(dropIndex);
	}

	/**
	 * Executes a drop index statement.
	 *
	 * @param index The index to drop.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropIndex(Index index, Connection cn) throws SQLException {
		DropIndex dropIndex = getDatabaseAdapter().getStatementDropIndex();
		dropIndex.setIndex(index);
		return executeStatement(dropIndex, cn);
	}

	/**
	 * Executes the drop table statement.
	 *
	 * @param table The table.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropTable(Table table) throws SQLException {
		DropTable dropTable = getDatabaseAdapter().getStatementDropTable();
		dropTable.setTable(table);
		return executeStatement(dropTable);
	}

	/**
	 * Executes the drop table statement.
	 *
	 * @param table The table.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeDropTable(Table table, Connection cn) throws SQLException {
		DropTable dropTable = getDatabaseAdapter().getStatementDropTable();
		dropTable.setTable(table);
		return executeStatement(dropTable, cn);
	}

	/**
	 * Executes a select and returns the cursor.
	 *
	 * @param select The select statement.
	 * @return The cursor.
	 * @throws SQLException
	 */
	public Cursor executeSelectCursor(Select select) throws SQLException {
		Connection cn = getConnection();
		return new Cursor(cn, select, false);
	}

	/**
	 * Executes a select and returns the cursor.
	 *
	 * @param select The select statement.
	 * @param forwardOnly A flg that indicates if the cursor is forwar only.
	 * @return The cursor.
	 * @throws SQLException
	 */
	public Cursor executeSelectCursor(Select select, boolean forwardOnly) throws SQLException {
		Connection cn = getConnection();
		return new Cursor(cn, select, forwardOnly);
	}

	/**
	 * Executes a select and returns the cursor.
	 *
	 * @param select The select statement.
	 * @param cn The connection.
	 * @return The cursor.
	 * @throws SQLException
	 */
	public Cursor executeSelectCursor(Select select, Connection cn) throws SQLException {
		return new Cursor(cn, select, false);
	}

	/**
	 * Executes a select and returns the cursor.
	 *
	 * @param select The select statement.
	 * @param cn The connection.
	 * @param forwardOnly A flg that indicates if the cursor is forwar only.
	 * @return The cursor.
	 * @throws SQLException
	 */
	public Cursor executeSelectCursor(Select select, Connection cn, boolean forwardOnly) throws SQLException {
		return new Cursor(cn, select, forwardOnly);
	}

	/**
	 * Executes a select recordset.
	 *
	 * @param select The select statement.
	 * @return The recordset.
	 * @throws SQLException
	 */
	public RecordSet executeSelectRecordSet(Select select) throws SQLException {
		return executeSelectCursor(select, true).getAllRecordsAndClose();
	}

	/**
	 * Executes a select recordset.
	 *
	 * @param select The select statement.
	 * @param maxRecords The maximum number of record to retrieve.
	 * @return The recordset.
	 * @throws SQLException
	 */
	public RecordSet executeSelectRecordSet(Select select, int maxRecords) throws SQLException {
		return executeSelectCursor(select, true).getAllRecordsAndClose(maxRecords);
	}

	/**
	 * Executes a select recordset. The connection stays opened.
	 *
	 * @param select The select statement.
	 * @param cn The connection.
	 * @return The recordset.
	 * @throws SQLException
	 */
	public RecordSet executeSelectRecordSet(Select select, Connection cn) throws SQLException {
		return executeSelectCursor(select, cn, true).getAllRecords();
	}

	/**
	 * Executes a select recordset. The connection stays opened.
	 *
	 * @param select The select statement.
	 * @param maxRecords The maximum number of records to retrieve.
	 * @param cn The connection.
	 * @return The recordset.
	 * @throws SQLException
	 */
	public RecordSet executeSelectRecordSet(Select select, int maxRecords, Connection cn) throws SQLException {
		return executeSelectCursor(select, cn, true).getAllRecords(maxRecords);
	}

	/**
	 * Executes a select record.
	 *
	 * @param table The table.
	 * @param primaryKey The primary key.
	 * @return The record.
	 * @throws SQLException
	 */
	public Record executeSelectPrimaryKey(Table table, OrderKey primaryKey) throws SQLException {
		View view = table.getSimpleView(null);
		Filter filter = table.getPrimaryKeyFilter(primaryKey);
		Select select = getDatabaseAdapter().getQuerySelect();
		select.setView(view);
		select.setFilter(filter);
		RecordSet recordSet = executeSelectRecordSet(select);
		if (!recordSet.isEmpty()) {
			return recordSet.get(0);
		}
		return null;
	}

	/**
	 * Executes a select record.
	 *
	 * @param table The table.
	 * @param primaryKey The primary key.
	 * @param cn The connection.
	 * @return The record.
	 * @throws SQLException
	 */
	public Record executeSelectPrimaryKey(Table table, OrderKey primaryKey, Connection cn) throws SQLException {
		View view = table.getSimpleView(null);
		Filter filter = table.getPrimaryKeyFilter(primaryKey);
		Select select = getDatabaseAdapter().getQuerySelect();
		select.setView(view);
		select.setFilter(filter);
		RecordSet recordSet = executeSelectRecordSet(select, cn);
		if (!recordSet.isEmpty()) {
			return recordSet.get(0);
		}
		return null;
	}

	/**
	 * Executes the insert statement.
	 *
	 * @param insert The insert statement.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeInsert(Insert insert) throws SQLException {
		return executeStatement(insert);
	}

	/**
	 * Executes the insert statement.
	 *
	 * @param insert The insert statement.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeInsert(Insert insert, Connection cn) throws SQLException {
		return executeStatement(insert, cn);
	}

	/**
	 * Executes the insert statement.
	 *
	 * @param table The table.
	 * @param record The record to insert.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeInsert(Table table, Record record) throws SQLException {
		Insert insert = getDatabaseAdapter().getStatementInsert();
		insert.setTable(table);
		insert.setRecord(record);
		return executeStatement(insert);
	}

	/**
	 * Executes the insert statement.
	 *
	 * @param table The table.
	 * @param record The record to insert.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeInsert(Table table, Record record, Connection cn) throws SQLException {
		Insert insert = getDatabaseAdapter().getStatementInsert();
		insert.setTable(table);
		insert.setRecord(record);
		return executeStatement(insert, cn);
	}

	/**
	 * Executes the update statement.
	 *
	 * @param update The update statement.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeUpdate(Update update) throws SQLException {
		return executeStatement(update);
	}

	/**
	 * Executes the update statement.
	 *
	 * @param update The update statement.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeUpdate(Update update, Connection cn) throws SQLException {
		return executeStatement(update, cn);
	}

	/**
	 * Executesthe update statement.
	 *
	 * @param table The table.
	 * @param record The record to update.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeUpdate(Table table, Record record) throws SQLException {
		Update update = getDatabaseAdapter().getStatementUpdate();
		update.setTable(table);
		update.setRecord(record);
		return executeStatement(update);
	}

	/**
	 * Executesthe update statement.
	 *
	 * @param table The table.
	 * @param record The record to update.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeUpdate(Table table, Record record, Connection cn) throws SQLException {
		Update update = getDatabaseAdapter().getStatementUpdate();
		update.setTable(table);
		update.setRecord(record);
		return executeStatement(update, cn);
	}

	/**
	 * Saves the record (insert if not exists, otherwise update)
	 *
	 * @param table The table.
	 * @param record The record.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeSave(Table table, Record record) throws SQLException {
		if (exists(table, record.getPrimaryKey())) {
			return executeUpdate(table, record);
		} else {
			return executeInsert(table, record);
		}
	}

	/**
	 * Saves the record (insert if not exists, otherwise update)
	 *
	 * @param table The table.
	 * @param record The record.
	 * @param cn The connection.
	 * @return The number of rows updated of zero if not applicable.
	 * @throws SQLException
	 */
	public int executeSave(Table table, Record record, Connection cn) throws SQLException {
		if (exists(table, record.getPrimaryKey(), cn)) {
			return executeUpdate(table, record, cn);
		} else {
			return executeInsert(table, record, cn);
		}
	}

	/**
	 * Get the sum for a list of columns.
	 *
	 * @param indexes The column indexes.
	 * @param view The view
	 * @param filter The filter.
	 * @return The list of sum values.
	 * @throws SQLException
	 */
	public List<Value> executeSelectSum(List<Integer> indexes, View view, Filter filter) throws SQLException {
		view = new View(view); // Save the source view
		view.removeAllColumns();
		view.setOrderBy(null);
		for (int index : indexes) {
			Column column = new Column(view.getColumn(index));
			column.setFunction("SUM(" + column.getNameWhere() + ")");
			view.addColumn(column);
		}

		Select select = getDatabaseAdapter().getQuerySelect();
		select.setView(view);
		select.setFilter(filter);
		RecordSet rs = executeSelectRecordSet(select);
		if (!rs.isEmpty()) {
			Record rc = rs.get(0);
			List<Value> values = new ArrayList<>();
			for (int i = 0; i < indexes.size(); i++) {
				values.add(rc.getValue(i));
			}
			return values;
		}
		return null;
	}

	/**
	 * Execute the sum for a column.
	 *
	 * @param index The column index.
	 * @param view The view.
	 * @param filter The filter.
	 * @return The sum value.
	 * @throws SQLException
	 */
	public Value executeSelectSum(int index, View view, Filter filter) throws SQLException {
		List<Integer> indexes = new ArrayList<>();
		indexes.add(index);
		List<Value> values = executeSelectSum(indexes, view, filter);
		if (values != null && !values.isEmpty()) {
			return values.get(0);
		}
		return null;
	}

	/**
	 * Checks if the argument primary key exists in the table.
	 *
	 * @param table The table.
	 * @param primaryKey The primary key.
	 * @return A boolean.
	 * @throws SQLException
	 */
	public boolean exists(Table table, OrderKey primaryKey) throws SQLException {
		return (executeSelectPrimaryKey(table, primaryKey) != null);
	}

	/**
	 * Checks if the argument primary key exists in the table.
	 *
	 * @param table The table.
	 * @param primaryKey The primary key.
	 * @param cn The connection.
	 * @return A boolean.
	 * @throws SQLException
	 */
	public boolean exists(Table table, OrderKey primaryKey, Connection cn) throws SQLException {
		return (executeSelectPrimaryKey(table, primaryKey, cn) != null);
	}

	/**
	 * Checks if the argument record exists in the table.
	 *
	 * @param table The table
	 * @param record The record.
	 * @return A boolean.
	 * @throws SQLException
	 */
	public boolean exists(Table table, Record record) throws SQLException {
		return (executeSelectPrimaryKey(table, record.getPrimaryKey()) != null);
	}

	/**
	 * Checks if the argument record exists in the table.
	 *
	 * @param table The table
	 * @param record The record.
	 * @param cn The connection.
	 * @return A boolean.
	 * @throws SQLException
	 */
	public boolean exists(Table table, Record record, Connection cn) throws SQLException {
		return (executeSelectPrimaryKey(table, record.getPrimaryKey(), cn) != null);
	}
}
